/**
 * Copyright (C) 2010 Cloudfarming <info@cloudfarming.nl>
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.cloudfarming.client.geoviewer.jxmap.map;

import com.vividsolutions.jts.geom.Point;
import java.awt.EventQueue;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.swing.ActionMap;
import javax.swing.JPanel;
import javax.swing.text.DefaultEditorKit;
import nl.cloudfarming.client.geoviewer.BaseLayer;
import nl.cloudfarming.client.geoviewer.Category;
import nl.cloudfarming.client.geoviewer.Layer;
import nl.cloudfarming.client.geoviewer.events.LayerEventKey;
import nl.cloudfarming.client.geoviewer.LayerInfo;
import nl.cloudfarming.client.geoviewer.LayerObject;
import nl.cloudfarming.client.geoviewer.LayerObjectNode;
import nl.cloudfarming.client.geoviewer.MapController;
import nl.cloudfarming.client.geoviewer.ObjectLayer;
import nl.cloudfarming.client.geoviewer.Preset;
import nl.cloudfarming.client.geoviewer.jxmap.layerlist.LayerListChildFactory;
import nl.cloudfarming.client.geoviewer.jxmap.layerlist.LayerListLayerNode;
import nl.cloudfarming.client.geoviewer.jxmap.layerlist.LayerListPanel;
import nl.cloudfarming.client.geoviewer.jxmap.layerlist.LayerListRootNode;
import nl.cloudfarming.client.model.PropertyViewerTopComponent;
import nl.cloudfarming.eventbus.GuiEvent;
import nl.cloudfarming.eventbus.GuiEventBus;
import nl.cloudfarming.eventbus.GuiEventKey;
import nl.cloudfarming.eventbus.GuiEventListener;
import org.jdesktop.swingx.JXMapViewer;
import org.jdesktop.swingx.mapviewer.GeoPosition;
import org.openide.awt.StatusDisplayer;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.nodes.Node;
import org.openide.util.Lookup;

/**
 * Controller of the rootmap panel.
 * 
 *
 * @author Timon Veenstra
 */
public class RootMapPanelController implements MapController, Lookup.Provider, ExplorerManager.Provider {

    private final RootMapPanel rootMapPanel;
    private final LayerListPanel layerListPanel = new LayerListPanel();
    private final Map<Layer, LayerPanel> layerPanelMap = new HashMap<Layer, LayerPanel>();
    private boolean active = false;
    private final ExplorerManager layerListEM = new ExplorerManager();
    private final Lookup lookup;
    private Layer activeLayer = null;

    public RootMapPanelController() {
        rootMapPanel = new RootMapPanel(layerListEM, layerListPanel);
        //
        // initialize the layers
        //
        initLayers();


        layerListEM.setRootContext(new LayerListRootNode(new LayerListChildFactory(this)));
        layerListEM.getRootContext().setDisplayName("");

//        ExplorerChangeListener changeListener = new ExplorerChangeListener();
//        manager.addPropertyChangeListener(changeListener);

        ActionMap map = new ActionMap();
        map.put(DefaultEditorKit.copyAction, ExplorerUtils.actionCopy(layerListEM));
        map.put(DefaultEditorKit.cutAction, ExplorerUtils.actionCut(layerListEM));
        map.put(DefaultEditorKit.pasteAction, ExplorerUtils.actionPaste(layerListEM));
        map.put("delete", ExplorerUtils.actionDelete(layerListEM, true)); // or false

        //
        // proxy layerlist lookup aswel as lookup from the layer data manager to enable save cookie
        //
        this.lookup = ExplorerUtils.createLookup(layerListEM, map);

        layerListEM.addPropertyChangeListener(new LayerListSelectionChangeListener());
        //
        // register property change listener to handle change in node selection.
        //

        GuiEventBus.addListener(new NewLayerListener());
        GuiEventBus.addListener(new RemovedLayerListener());

        MapController.Registry.addMapController(this);

        //
        // init the layerlist
        //
        LayerDropTargetListener dtl = new LayerDropTargetListener();
        DropTarget dt = new DropTarget(layerListPanel, dtl);
        dt.setDefaultActions(DnDConstants.ACTION_COPY);
        dt.setActive(true);
        layerListPanel.setDropTarget(dt);

    }

    private void initLayers() {
        //TODO retrieve layers from properties
    }

    public JPanel getPanel() {
        return this.rootMapPanel;
    }

    private void repaintLayer(final Layer layer) {
        if (EventQueue.isDispatchThread()) {
            if (layerPanelMap.containsKey(layer)) {
                layerPanelMap.get(layer).repaint();
            }
        } else {
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    if (layerPanelMap.containsKey(layer)) {
                        layerPanelMap.get(layer).repaint();
                    }

                }
            });
        }
    }

    @Override
    public void removeLayer(final Layer layer) {
        assert layerPanelMap.containsKey(layer);
        LayerPanel layerPanel = layerPanelMap.get(layer);
        rootMapPanel.removeLayerPanel(layerPanel);
        layerPanelMap.remove(layer);
        refreshLayerList();
    }

    private void refreshLayerList() {
        if (this.layerListEM.getRootContext() instanceof LayerListRootNode) {
            ((LayerListRootNode) this.layerListEM.getRootContext()).refresh();
        }
    }

    private void addLayer(final Layer layer) {

        //
        // register listener for pallete change
        //
        layer.addPropertyChangeListener(BaseLayer.PROPERTY_PALETTE, new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                repaintLayer(layer);
            }
        });

        LayerPanel layerPanel;

        if (layer instanceof ObjectLayer) {
            layerPanel = new ObjectLayerPanel(this, rootMapPanel, (ObjectLayer) layer, getViewer());
            layer.addPropertyChangeListener(ObjectLayer.PROPERTY_OBJECTS, new PropertyChangeListener() {

                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    repaintLayer(layer);
                }
            });

            if (layer.getDataProvider() != null) {
                layer.getDataProvider().getExplorerManager().addPropertyChangeListener(new SelectionChangeListener(layer));
            }


        } else {
            layerPanel = new ImageLayerPanel(this, rootMapPanel, layer, getViewer());
        }



        layerPanelMap.put(layer, layerPanel);
        rootMapPanel.addLayerPanel(layerPanel);
        refreshLayerList();

    }

    @Override
    public Set<Layer> getLayers() {
        return Collections.unmodifiableSet(layerPanelMap.keySet());
    }

    @Override
    public void setLayerOrder(int[] order, Category category) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public LayerInfo getLayerInfo(Layer layer) {
        assert layerPanelMap.containsKey(layer);
        return layerPanelMap.get(layer).getInfo();
    }

    @Override
    public Preset getCurrentPreset() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public String getMapName() {
        return "map:" + System.identityHashCode(this);
    }

    JXMapViewer getViewer() {
        return rootMapPanel.getMapViewer();
    }

    @Override
    public void zoomToObject(LayerObject layerObject, Layer layer) {
        if (!this.layerPanelMap.containsKey(layer)) {
            return;
        }

        Point centroid = layerObject.getGeometry().getCentroid();
        GeoPosition geoPosition = new GeoPosition(centroid.getY(), centroid.getX());
        rootMapPanel.getMapViewer().setCenterPosition(geoPosition);
        rootMapPanel.getMapViewer().setZoom(3);
    }

    public boolean isActive() {
        return active;
    }

    public void setActive(boolean active) {
        this.active = active;
    }

    @Override
    public void setActiveLayer(Layer layer) {
        assert this.layerPanelMap.containsKey(layer);

        if (layer.isInterActive()) {
            rootMapPanel.setActiveLayer((ObjectLayerPanel) this.layerPanelMap.get(layer));
            this.activeLayer = layer;
        } else {
            rootMapPanel.setActiveLayer(null);
            this.activeLayer = null;
        }
    }

    @Override
    public Lookup getLookup() {
        return this.lookup;
    }

    @Override
    public ExplorerManager getExplorerManager() {
        return this.layerListEM;
    }

    private class NewLayerListener implements GuiEventListener<Layer> {

        @Override
        public void onEvent(GuiEvent<Layer> event) {
            addLayer(event.getContent());
        }

        @Override
        public boolean listensTo(GuiEventKey guiEventKey) {
            return LayerEventKey.NEW_LAYER.equals(guiEventKey);
        }

        @Override
        public String getModuleName() {
            return "geoviewer-jxmap";
        }
    }

    private class RemovedLayerListener implements GuiEventListener<Layer> {

        @Override
        public void onEvent(GuiEvent<Layer> event) {
            removeLayer(event.getContent());
        }

        @Override
        public boolean listensTo(GuiEventKey guiEventKey) {
            return LayerEventKey.REMOVED_LAYER.equals(guiEventKey);
        }

        @Override
        public String getModuleName() {
            return "geoviewer-jxmap";
        }
    }

    /**
     * listens to changes in object selection
     */
    private class SelectionChangeListener implements PropertyChangeListener {

        private final Layer Layer;

        public SelectionChangeListener(Layer Layer) {
            this.Layer = Layer;
        }

        @Override
        public void propertyChange(PropertyChangeEvent event) {
            //
            // only change selection if:
            //  - layerPanelMap does contain layers
            //  - the controller is not active ( otherwise we are the ones initiating the event in the first place
            //  - the selected node property have been changed (duh)
            //  - there is an active layer
            //  - the active layer is also interactive ( ie contains selectable objects)
            if (layerPanelMap.containsKey(Layer) && !isActive() && ExplorerManager.PROP_SELECTED_NODES.equals(event.getPropertyName()) && activeLayer != null && activeLayer.equals(Layer)) {
                Set<LayerObject> objects = new HashSet<LayerObject>();
                Node[] nodes = (Node[]) event.getNewValue();
                for (Node node : nodes) {
                    if (node instanceof LayerObjectNode) {
                        objects.add(((LayerObjectNode) node).getLayerObject());
                    }
                }
                layerPanelMap.get(Layer).setSelectedObjects(objects);
            }
        }
    }

    /**
     * listens to changes in the layer selection
     */
    private class LayerListSelectionChangeListener implements PropertyChangeListener {

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            // handle change of node selection
            if (ExplorerManager.PROP_SELECTED_NODES.equals(evt.getPropertyName())) {
                Node[] nodes = (Node[]) evt.getNewValue();
                for (Node node : nodes) {
                    if (node instanceof LayerListLayerNode) {
                        Layer layer = ((LayerListLayerNode) node).getLayer();
                        setActiveLayer(layer);
                        PropertyViewerTopComponent.getDefault().setDataProvider(layer.getDataProvider());
                    }
                }
            }
        }
    }

    public class LayerDropTargetListener implements DropTargetListener {

        @Override
        public void dragEnter(DropTargetDragEvent dtde) {
        }

        @Override
        public void dragExit(DropTargetEvent dtde) {
        }

        @Override
        public void dragOver(DropTargetDragEvent dtde) {
        }

        @Override
        public void dropActionChanged(DropTargetDragEvent dtde) {
        }

        @Override
        public void drop(DropTargetDropEvent dtde) {
            if (dtde.isDataFlavorSupported(Layer.DATA_FLAVOR)) {
                try {
                    Object transData = dtde.getTransferable().getTransferData(Layer.DATA_FLAVOR);
                    if (transData instanceof Layer) {
                        dtde.acceptDrop(DnDConstants.ACTION_COPY);
                        Layer c = (Layer) dtde.getTransferable().getTransferData(Layer.DATA_FLAVOR);
                        StatusDisplayer.getDefault().setStatusText(c.getName());
                        addLayer(c);
                    }
                } catch (UnsupportedFlavorException ufe) {
                    dtde.rejectDrop();
                    dtde.dropComplete(true);
                } catch (IOException ioe) {
                    dtde.rejectDrop();
                    dtde.dropComplete(false);
                }
            } else {
                dtde.rejectDrop();
                dtde.dropComplete(false);
            }
        }
    }
}
